package com.qk.util;

import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.*;
import com.qk.enums.ApiEnum;
import com.qk.exception.AliyunOSSException;
import com.qk.exception.CommonException;
import com.qk.util.property.AliyunOSSProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;

import java.io.ByteArrayInputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Slf4j
@Component
@SuppressWarnings("all")
public class AliyunOSSOperator {

    @Autowired
    private AliyunOSSProperties aliyunOSSProperties;

    @Autowired
    private STSUtils stsUtils;

    public static final String PRE_URL = "https://";
    public static final String ENCODING_TYPE = "url";
    public static final String FOLDER_GAP = "/";
    public static final String TIME_FORMATTER = "yyyy/MM/dd";
    public static final Integer GET_BATCH_SIZE = 1000;

    public String uploadFile(byte[] content, String objectName) throws Exception {
        LocalDateTime now = LocalDateTime.now();
        String objectKey = StrBuilder.create()
                .append(now.getYear())
                .append(FOLDER_GAP)
                .append(now.getMonthValue())
                .append(FOLDER_GAP)
                .append(now.getDayOfMonth())
                .append(FOLDER_GAP)
                .append(objectName)
                .toString();
        handleException(() -> {
            // 如果需要上传时设置存储类型和访问权限，请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);
            OSS ossClient = createOSSClient();
            ossClient.putObject(new PutObjectRequest(
                    aliyunOSSProperties.getBucketName(),
                    objectKey,
                    new ByteArrayInputStream(content)
            ));
            return ossClient;
        });

        // 4. 返回文件访问URL
        String url = StrBuilder.create()
                .append(PRE_URL)
                .append(aliyunOSSProperties.getBucketName())
                .append(".")
                .append(StrUtil.sub(aliyunOSSProperties.getEndpoint(), 8, StrUtil.length(aliyunOSSProperties.getEndpoint())))
                .append("/")
                .append(objectKey)
                .toString();
        log.info("文件上传成功，访问URL：{}", url);
        return url;
    }

    public List<String> deleteFileBatch(List<OSSObjectSummary> ossObjectSummaries) {
        List<String> deleteObjectKeys = ossObjectSummaries.stream()
                .map(this::OSSObjectSummaryToKey)
                .collect(Collectors.toList());
        AtomicReference<List<String>> deletedResult = new AtomicReference<>();
        handleException(() -> {
            OSS ossClient = createOSSClient();
            deletedResult.set(ossClient
                    .deleteObjects(new DeleteObjectsRequest(aliyunOSSProperties.getBucketName())
                            .withKeys(deleteObjectKeys)
                            .withEncodingType(ENCODING_TYPE))
                    .getDeletedObjects());
            return ossClient;
        });
        log.info("文件删除成功，删除文件：{}", deletedResult.get().toString());
        return deletedResult.get();
    }

    public void copyFile(String sourceObjectName, String targetObjectName) {
        handleException(() -> {
            OSS ossClient = createOSSClient();
            // 创建CopyObjectRequest对象。
            CopyObjectRequest copyObjectRequest = new CopyObjectRequest(
                    aliyunOSSProperties.getBucketName(), sourceObjectName,
                    aliyunOSSProperties.getBucketName(), targetObjectName);

            // 设置新的文件元数据。
            ObjectMetadata meta = new ObjectMetadata();
            // 支持文件
            meta.setContentType("application/octet-stream");
            // 指定CopyObject操作时是否覆盖同名目标Object。此处设置为true，表示禁止覆盖同名Object。
            // meta.setHeader("x-oss-forbid-overwrite", "true");
            // 指定拷贝的源地址。
            // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE, "/examplebucket/recode-test.txt");
            // 如果源Object的ETag值和您提供的ETag相等，则执行拷贝操作，并返回200 OK。
            // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE_IF_MATCH, "5B3C1A2E053D763E1B002CC607C5****");
            // 如果源Object的ETag值和您提供的ETag不相等，则执行拷贝操作，并返回200 OK。
            // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE_IF_NONE_MATCH, "5B3C1A2E053D763E1B002CC607C5****");
            // 如果指定的时间等于或者晚于文件实际修改时间，则正常拷贝文件，并返回200 OK。
            // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE_IF_UNMODIFIED_SINCE, "2021-12-09T07:01:56.000Z");
            // 如果源Object在指定时间后被修改过，则执行拷贝操作。
            // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE_IF_MODIFIED_SINCE, "2021-12-09T07:01:56.000Z");
            // 指定设置目标Object元数据的方式。此处设置为COPY，表示复制源Object的元数据到目标Object。
            meta.setHeader(OSSHeaders.COPY_OBJECT_METADATA_DIRECTIVE, "COPY");
            // 指定OSS创建目标Object时使用的服务器端加密算法。
            // meta.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
            // 表示KMS托管的用户主密钥，该参数仅在x-oss-server-side-encryption为KMS时有效。
            // meta.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION_KEY_ID, "9468da86-3509-4f8d-a61e-6eab1eac****");
            // 指定OSS创建目标Object时的访问权限，此处设置为Private，表示只有Object的拥有者和授权用户有该Object的读写权限，其他用户没有权限操作该Object。
            // meta.setHeader(OSSHeaders.OSS_OBJECT_ACL, CannedAccessControlList.Private);
            // 指定Object的存储类型。此处设置为Standard，表示标准存储类型。
            // meta.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard);
            // 指定Object的对象标签，可同时设置多个标签。
            // meta.setHeader(OSSHeaders.OSS_TAGGING, "a:1");
            // 指定设置目标Object对象标签的方式。此处设置为COPY，表示复制源Object的对象标签到目标Object。
            // meta.setHeader(OSSHeaders.COPY_OBJECT_TAGGING_DIRECTIVE, "COPY");
            copyObjectRequest.setNewObjectMetadata(meta);
            CopyObjectResult copyObjectResult = ossClient.copyObject(copyObjectRequest);
            log.info("ETag: {} LastModified: {}", copyObjectResult.getETag(), copyObjectResult.getLastModified());
            return ossClient;
        });
    }

    public void copyFileBatch(Map<String, String> objectNameCopyMap) {
        handleException(() -> {
            OSS ossClient = createOSSClient();
            objectNameCopyMap.forEach((sourceObjectName, targetObjectName) -> {
                CopyObjectRequest copyObjectRequest = new CopyObjectRequest(aliyunOSSProperties.getBucketName(), sourceObjectName,
                        aliyunOSSProperties.getBucketName(), targetObjectName);
                ObjectMetadata meta = new ObjectMetadata();
                meta.setContentType("application/octet-stream");
                meta.setHeader(OSSHeaders.COPY_OBJECT_METADATA_DIRECTIVE, "COPY");
                copyObjectRequest.setNewObjectMetadata(meta);
                CopyObjectResult copyObjectResult = ossClient.copyObject(copyObjectRequest);
                log.info("ETag: {} LastModified: {}", copyObjectResult.getETag(), copyObjectResult.getLastModified());
            });
            return ossClient;
        });
    }

    public List<String> getAllFilesUrl() {
        AtomicReference<List<String>> urls = new AtomicReference<>();
        handleException(() -> {
            ArrayList<String> objectNames = new ArrayList<>();
            String nextContinuationToken = null;
            ListObjectsV2Result listObjectsV2Result = null;
            OSS ossClient = createOSSClient();
            do {
                ListObjectsV2Request listObjectsRequest = new ListObjectsV2Request(aliyunOSSProperties.getBucketName())
                        .withEncodingType(ENCODING_TYPE).withMaxKeys(GET_BATCH_SIZE);
                listObjectsRequest.setContinuationToken(nextContinuationToken);
                listObjectsV2Result = ossClient.listObjectsV2(listObjectsRequest);
                List<String> listStr = listObjectsV2Result
                        .getObjectSummaries()
                        .stream()
                        .map(this::OSSObjectSummaryToUrl)
                        .collect(Collectors.toList());
                objectNames.addAll(listStr);
                nextContinuationToken = listObjectsV2Result.getNextContinuationToken();
            } while (listObjectsV2Result.isTruncated());
            urls.set(objectNames);
            return ossClient;
        });
        log.info("获取文件成功: {}" + urls.get());
        return urls.get();
    }

    public List<String> getFilesUrlByPrefix(String prefix) {
        AtomicReference<List<String>> urls = new AtomicReference<>();
        handleException(() -> {
            ArrayList<String> objectNames = new ArrayList<>();
            String nextContinuationToken = null;
            ListObjectsV2Result listObjectsV2Result = null;
            OSS ossClient = createOSSClient();
            do {
                ListObjectsV2Request listObjectsRequest = new ListObjectsV2Request(aliyunOSSProperties.getBucketName())
                        .withEncodingType(ENCODING_TYPE).withPrefix(prefix).withDelimiter(FOLDER_GAP).withMaxKeys(GET_BATCH_SIZE);
                listObjectsRequest.setContinuationToken(nextContinuationToken);
                listObjectsV2Result = ossClient.listObjectsV2(listObjectsRequest);
                List<String> listStr = listObjectsV2Result
                        .getObjectSummaries()
                        .stream()
                        .map(this::OSSObjectSummaryToUrl)
                        .collect(Collectors.toList());
                objectNames.addAll(listStr);
                nextContinuationToken = listObjectsV2Result.getNextContinuationToken();
            } while (listObjectsV2Result.isTruncated());
            urls.set(objectNames);
            return ossClient;
        });
        log.info("获取文件成功: {}" + urls.get());
        return urls.get();
    }

    public String OSSObjectSummaryToUrl(OSSObjectSummary objectSummary) {
        LocalDateTime now = objectSummary.getLastModified().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        return StrBuilder.create()
                .append(PRE_URL)
                .append(aliyunOSSProperties.getBucketName())
                .append(".")
                .append(StrUtil.sub(aliyunOSSProperties.getEndpoint(), 8, StrUtil.length(aliyunOSSProperties.getEndpoint())))
                .append("/")
                .append(now.getYear())
                .append(FOLDER_GAP)
                .append(now.getMonthValue())
                .append(FOLDER_GAP)
                .append(now.getDayOfMonth())
                .append(FOLDER_GAP)
                .append(URLDecoder.decode(objectSummary.getKey(), StandardCharsets.UTF_8))
                .toString();
    }

    public String OSSObjectSummaryToKey(OSSObjectSummary objectSummary) {
        LocalDateTime now = objectSummary.getLastModified().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        return StrBuilder.create()
                .append(now.getYear())
                .append(FOLDER_GAP)
                .append(now.getMonthValue())
                .append(FOLDER_GAP)
                .append(now.getDayOfMonth())
                .append(FOLDER_GAP)
                .append(URLDecoder.decode(objectSummary.getKey(), StandardCharsets.UTF_8))
                .toString();
    }

    /**
     * 基于文件名中的日期信息进行迁移
     */
    public void organizeFilesByDateInFilename() {
        AtomicReference<List<OSSObjectSummary>> objectSummaries = new AtomicReference<>();
        handleException(() -> {
            List<OSSObjectSummary> objectSummariesTemp = new ArrayList<>();
            String nextContinuationToken = null;
            ListObjectsV2Result listObjectsV2Result = null;
            OSS ossClient = createOSSClient();
            do {
                ListObjectsV2Request listObjectsV2Request = new ListObjectsV2Request(aliyunOSSProperties.getBucketName())
                        .withEncodingType(ENCODING_TYPE).withMaxKeys(GET_BATCH_SIZE);
                listObjectsV2Request.setContinuationToken(nextContinuationToken);
                listObjectsV2Result = ossClient.listObjectsV2(listObjectsV2Request);
                objectSummariesTemp.addAll(listObjectsV2Result.getObjectSummaries());
                nextContinuationToken = listObjectsV2Result.getNextContinuationToken();
                objectSummaries.set(objectSummariesTemp);
            } while (listObjectsV2Result.isTruncated());
            return ossClient;
        });
        log.info("获取文件成功: {}", objectSummaries.get().toString());
        List<OSSObjectSummary> ossObjectSummaries = objectSummaries.get().stream()
                .filter(x -> !StrUtil.contains(URLDecoder.decode(x.getKey(), StandardCharsets.UTF_8), FOLDER_GAP))
                .collect(Collectors.toList());
//            复制
//        copyFileBatch(list.stream().collect(Collectors.toMap(OSSObjectSummary::getKey, this::OSSObjectSummaryToKey)));
//            删除
        deleteFileBatch(ossObjectSummaries);
    }

    public OSS createOSSClient() {
        // 1. 获取STS临时令牌
        AssumeRoleResponse.Credentials credentials = stsUtils.getSTSToken(aliyunOSSProperties.getBucketName());
        if (ObjectUtil.isNull(credentials)) {
            log.error("无法获取STS令牌，上传终止");
            CommonException.throwException(ApiEnum.UPLOAD_ERROR);
        }

        // 配置客户端
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        // 2. 使用临时令牌创建OSS客户端
        return OSSClientBuilder.create()
                .endpoint(aliyunOSSProperties.getEndpoint())
                .region(aliyunOSSProperties.getRegion())
                .credentialsProvider(CredentialsProviderFactory.newDefaultCredentialProvider(
                        credentials.getAccessKeyId(),
                        credentials.getAccessKeySecret(),
                        credentials.getSecurityToken()
                ))
                .clientConfiguration(clientBuilderConfiguration)
                .build();
    }

    public void handleException(Supplier<OSS> supplier) {
        OSS ossClient = null;
        try {
            ossClient = supplier.get();
        } catch (OSSException oe) {
            log.error("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            log.error("Error Message:{}", oe.getErrorMessage());
            log.error("Error Code:{}", oe.getErrorCode());
            log.error("Request ID:{}", oe.getRequestId());
            log.error("Host ID:{}", oe.getHostId());
            AliyunOSSException.throwOSSException(oe);
        } catch (ClientException ce) {
            log.error("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            log.error("Error Message:{}", ce.getMessage());
            AliyunOSSException.throwClientException(ce);
        } finally {
            if (ObjectUtil.isNotEmpty(ossClient)) {
                ossClient.shutdown();
            } else {
                log.warn("OSS客户端未初始化");
            }
        }
    }
}